/**
 * Copyright (C) 2008 by The Regents of the University of California
 * Redistribution of this file is permitted under the terms of the GNU
 * Public License (GPL).
 *
 * @author Junghoo "John" Cho <cho AT cs.ucla.edu>
 * @date 3/24/2008
 */

#include <cstdio>
#include <iostream>
#include <fstream>
#include "Bruinbase.h"
#include "SqlEngine.h"
#include <vector>
#include <string>

using namespace std;

// external functions and variables for load file and sql command parsing 
extern FILE* sqlin;
int sqlparse(void);

string indexedfile[512];
int indexed = 0;
RC SqlEngine::run(FILE* commandline)
{
	fprintf(stdout, "Bruinbase> ");
	
	// set the command line input and start parsing user input
	sqlin = commandline;
	sqlparse();  // sqlparse() is defined in SqlParser.tab.c generated from
	// SqlParser.y by bison (bison is GNU equivalent of yacc)
	
	return 0;
}

RC SqlEngine::select(int attr, const string& table, const vector<SelCond>& cond)
{
	RecordFile rf;   // RecordFile containing the table
	RecordId   rid;  // record cursor for table scanning
	BTreeIndex btidx;
	
	RC     rc;
	int    key;     
	string value;
	int    count;
	int    diff;
	
	// check if index exists
	bool hasidxtbl = false;

	string mytable = table.c_str();
	for(int i = 0; i<indexed; i++){
		string comptable = indexedfile[i].c_str();
		if(mytable.compare(comptable)==0){
			hasidxtbl = true;
			if (btidx.open(table + ".idx", 'r') != 0) {
				fprintf(stderr, "Error: index for %s does not exist\n", table.c_str());
			}
		}
	}	

	// open the table file
	if ((rc = rf.open(table + ".tbl", 'r')) < 0) {
		fprintf(stderr, "Error: table %s does not exist\n", table.c_str());
		return rc;
	}

	// before we even do anything, we have to check if we have to use btidx 
	// instead of just the normal tbl
	// if NE, use table
	// if EQ, LT, GT, LE, GE appear anywhere else, use btidx
	int upp_bound = 2100000000; // use only if we see an upper bound value, else don't use
	// and read to the end
	int low_bound = -1; // always will need to use if we see a range
	bool specifickey = false; // if true, then we see a EQ for key
	bool range = false; // if true then we saw a valid >, <, <== etc. and must use
	// index, otherwise, use tablescan
	int tempval;
	bool hasvalcond = false; // true if we see a condition with value
	bool invalidsearch = false; // if range is out of place or key is duplicate
	if(cond.size()>0){
		// if size of the array is greater than 0, then there are conditions
		// if no conditions, use table
		for(int i = 0; i < cond.size(); i++){
			if(cond[i].attr==1){
				tempval = atoi(cond[i].value);
				// we have found a key attribute
				switch (cond[i].comp) {
					case SelCond::EQ:
						// found 'key == tempval'
						if(specifickey==false){
							// have not found specific key to search for yet
							upp_bound = low_bound = tempval;
							specifickey = true; // we have now seen it
						}else{
							// means we already found a specific key
							// add this and it makes 2 and it's impossible to search for 2 
							// specific keys
							if(upp_bound!=tempval){
								// checks for specifickey that is not the same as the current one
								// technically, we can check tempval against upp_bound or low_bound
								// since both those values are the same once we find the specifickey
								invalidsearch = true;
							}
						}
						break;
					case SelCond::NE:
						// for this case, we should check it as if we were
						// checking value of tuples (check as is, no use of index)
						break;
					case SelCond::LE:
						// found 'key <= tempval'
						if(specifickey==false){
							// this means we have not found a specific key to search for yet
							if(upp_bound>tempval){
								upp_bound = tempval;
							}
						}else{
							// we have found a specific key 
							// have to check that specific key range is inside the lower bound
							if(upp_bound>tempval){
								invalidsearch = true;
							}
						}
						range = true;
						break;
					case SelCond::LT:
						// found 'key < tempval'
						if(specifickey==false){
							// this means we have not found a specific key to search for yet
							if(upp_bound>(tempval-1)){
								upp_bound = tempval - 1;
							}
						}else{
							// we have found a specific key 
							// have to check that specific key range is inside the lower bound
							if(upp_bound>(tempval-1)){
								invalidsearch = true;
							}
						}
						range = true;
						break;
					case SelCond::GE:
						// found 'key >= tempval'
						if(specifickey==false){
							// this means we have not found a specific key to search for yet
							if(low_bound<tempval){
								low_bound = tempval;
							}
						}else{
							// we have found a specific key 
							// have to check that specific key range is inside the lower bound
							if(low_bound<tempval){
								invalidsearch = true;
							}
						}
						range = true;
						break;
					case SelCond::GT:
						// found 'key > tempval'
						if(specifickey==false){
							// this means we have not found a specific key to search for yet
							if(low_bound<(tempval+1)){
								low_bound = tempval + 1;
							}
						}else{
							// we have found a specific key 
							// have to check that specific key range is inside the lower bound
							if(low_bound<(tempval-1)){
								invalidsearch = true;
							}
						}
						range = true;
						break;
				}
			}else{
				// attr == 2 means one of the conditions are of value type
				hasvalcond = true; 
			}
		}
	}

	if((upp_bound - low_bound) < 0){
		// a condition where upper bound is greater than lower bound
		// putting our search into no range
		invalidsearch = true;
	}

	if(invalidsearch==true){
		goto exit_select;
	}

	if(range&&hasidxtbl){
		// we pretty much have the upper and lower bounds by this time
		// we are also sure the search is not invalid, this is handled
		// by the code directly above this block
		count = 0;
		IndexCursor idxcrsr;
		/*if(btidx.open(table + ".idx", 'r') < 0){

		}*/

		bool has_upp_bound = true;
		if(upp_bound==0){
			// then upper bound was never changed and we have to search to the end
			has_upp_bound = false;
		}

		// remember that value can also use EQ, NE, LE, LT, GE, GT comparators
		if(specifickey){
			// specific key exists
			// only 1 search needed
			if ((rc = btidx.locate(upp_bound, idxcrsr)) != 0) {
				btidx.close();
				return rc;
			}
		}else{
			// no specific key so we have to go through the whole range 
			cout<<" "<<upp_bound<<" "<<low_bound<<endl;
			bool ic_init = false;
			if(has_upp_bound){
				// go from low_bound and readForward until upp_bound
				// optimization: check the attr to see if we need to go to recordfile to search or not
				bool upp_bound_reached = false;
				while(1){
					if(!ic_init){
						btidx.locate(low_bound, idxcrsr);
						ic_init = true;
					}else{
						if(upp_bound_reached==false){
							// we have not reached upperbound yet
							if(btidx.readForward(idxcrsr, key, rid)<0){
								// we have reached the end of the files so
								// by default, we have also reached the upper bound
								//fprintf(stderr, "Error: while reading readingforward");
								upp_bound_reached = true;
							}
							cout<<"found key: "<<key<<endl;
							if(upp_bound==key){
								// upper bound has equaled the key we have read to
								upp_bound_reached = true;
							}
							// by now we have the key and rid of the tuple
							// we have to check if we satisfy the condition
							// if we satisfy, we need to check for what needs to be shown
							// else, we do nothing and finish this specific iteration

							// iterate through all the conditions
							bool condition_met = true; // set to false if any condition below not met
							for(int i = 0; i<cond.size(); i++){
								// ignore the ones we already know
								if((hasvalcond == true)||(attr==2)||(attr==3)){
									// there is a condition for value
									// we must get the value, not just the key
									if((rc=rf.read(rid, key, value))<0){
										//fprintf(stderr, "Error: while reading a tuple here");
										goto exit_select;
									}
								}
								if((cond[i].attr == 1)&&(cond[i].comp == SelCond::NE)){
									// condition is a key with comp NE
									// check here if we use SelCond::NE or just NE
									// <><><><><><><><><><><><><><><><><><><><><><><><><><><><>
									int temp = atoi(cond[i].value);
									if(temp==key){
										condition_met = false;
									}
								}else if(cond[i].attr == 2){
									// condition is a value
									// this can be any of the EQ, LT, GT, LE, GE, NE
									diff = strcmp(value.c_str(), cond[i].value);
									// if diff == 0, strings equal
									// if diff > 0, 1st string greater than 2nd
									// if deff < 0, 1st string less than 2nd
									switch(cond[i].comp){
										// check here if we need to do some conversion of strings
										// <><><><><><><><><><><><><><><><><><><><><><><><><><><><>
										case SelCond::EQ:
											if(diff!=0){
												condition_met = false;
											}
											break;
										case SelCond::LT:
											// cond.val < 10, val = 10 (not ok)
											// cond.val < 10, val = 9 (ok)
											if(diff>=0){
												condition_met = false;
											}
											break;
										case SelCond::GT:
											// cond.val > 10, val = 10 (not ok)
											// cond.val > 10, val = 11 (ok)
											if(diff<=0){
												condition_met = false;
											}
											break;
										case SelCond::LE:
											// cond.val <= 10, val = 11 (not ok)
											// cond.val <= 10, val = 10 (ok)
											// cond.val <= 10, val = 9 (ok)
											if(diff>0){
												condition_met = false;
											}
											break;
										case SelCond::GE:	
											// cond.val >= 10, val = 11 (ok)
											// cond.val >= 10, val = 10 (ok)
											// cond.val >= 10, val = 9 (not ok)
											if(diff<0){
												condition_met = false;
											}
											break;
										case SelCond::NE:
											if(diff==0){
												condition_met = false;
											}
											break;
									}
								}else{
									// condition is a key with comp != NE
									// we should do nothing here as we already have the info we need
								}
							}
							// we start printing out now
							if(condition_met==true){
								count++;
								switch (attr) {
									case 1:  // SELECT key
										fprintf(stdout, "%d\n", key);
										break;
									case 2:  // SELECT value
										fprintf(stdout, "%s\n", value.c_str());
										break;
									case 3:  // SELECT *
										fprintf(stdout, "%d '%s'\n", key, value.c_str());
										break;
								}
							}

						}else{
							// goto somewhere to end select
							// we have reached upper bound
							// print matching tuple count if "select count(*)"
							if (attr == 4) {
								fprintf(stdout, "%d\n", count);
							}
							rc = 0;
							goto exit_select;
						}
					}
				}
			}
			
		}

	}else{
		// scan the table file from the beginning
		rid.pid = rid.sid = 0;
		count = 0;
		while (rid < rf.endRid()) {
			// read the tuple
			if ((rc = rf.read(rid, key, value)) < 0) {
				fprintf(stderr, "Error: while reading a tuple from table %s\n", table.c_str());
				goto exit_select;
			}
			
			// check the conditions on the tuple
			for (unsigned i = 0; i < cond.size(); i++) {
				// compute the difference between the tuple value and the condition value
				switch (cond[i].attr) {
					case 1:
						diff = key - atoi(cond[i].value);
						break;
					case 2:
						diff = strcmp(value.c_str(), cond[i].value);
						break;
				}
				
				// skip the tuple if any condition is not met
				switch (cond[i].comp) {
					case SelCond::EQ:
						if (diff != 0) goto next_tuple;
						break;
					case SelCond::NE:
						if (diff == 0) goto next_tuple;
						break;
					case SelCond::GT:
						if (diff <= 0) goto next_tuple;
						break;
					case SelCond::LT:
						if (diff >= 0) goto next_tuple;
						break;
					case SelCond::GE:
						if (diff < 0) goto next_tuple;
						break;
					case SelCond::LE:
						if (diff > 0) goto next_tuple;
						break;
				}
			}
			
			// the condition is met for the tuple. 
			// increase matching tuple counter
			count++;
			
			// print the tuple 
			switch (attr) {
				case 1:  // SELECT key
					fprintf(stdout, "%d\n", key);
					break;
				case 2:  // SELECT value
					fprintf(stdout, "%s\n", value.c_str());
					break;
				case 3:  // SELECT *
					fprintf(stdout, "%d '%s'\n", key, value.c_str());
					break;
			}
			
			// move to the next tuple
		next_tuple:
			++rid;
		}
		
		// print matching tuple count if "select count(*)"
		if (attr == 4) {
			fprintf(stdout, "%d\n", count);
		}
		rc = 0;
	}
	
	// close the table file and return
exit_select:
	btidx.close();
	rf.close();
	return rc;
}

// bool index options not implemented yet
// also don't know if appending to an existing file works... (works)
// limitations to this code: getline only works if 1 entry is 1 line. 
// Otherwise, if it was all consecutive, it won't work. 
RC SqlEngine::load(const string& table, const string& loadfile, bool index)
{
	/* your code here */
	RecordFile rf;
	RecordId rid;
	int key;
	string value;
	rid.pid = rid.sid = 0; // initialize to 0
	rf.open(table + ".tbl", 'w'); // open a table with 'w' write option
	
	// check if loadfile exists
	ifstream my_file(loadfile.c_str());
	if (my_file.bad()) {
		// The file to read from does not exist
		return -1; // output error
	}
	
	BTreeIndex btidx;
	// if with index == true, create idx table
	if(index) {
		btidx.open(table + ".idx", 'w');
		indexedfile[indexed] = table.c_str();
		indexed++;
	}

	// open file and put all into vector/array of strings 
	// pass strings one by one into parseLoadLine
	vector<string> data (20); // we start by giving the vector of strings a fixed size 20
	int dcount = 0;
	while(!my_file.eof()){
		string s;
		getline(my_file, s);
		data[dcount] = s;	
		// code below to check if we need to add more space to the vector
		int dsize = data.size();
		if(dsize==dcount){
			data.resize(dsize*2); // resize data to twice its capacity
		}
		dcount++; // increase for next iteration
	}
	my_file.close(); // finished reading from loadfile
	
	int dcount2 = dcount - 1; // actual number of strings in vector
	// note: we resize vectors to twice their normal size so we will inevitably have indexes with
	// garbage data inside them
	
	// while loadfile not EOF, keep parsing
	for(int i = 0; i < dcount2; i++){
			if(parseLoadLine(data[i], key, value)!=0){
				return RC_INVALID_ATTRIBUTE;
			}
			if(rf.append(key, value, rid)==0){
				// if appending rf works, then we insert to btidx if index = true
				if(index){
					if(btidx.insert(key, rid) != 0) {
						// if insert fails
						return RC_FILE_WRITE_FAILED;
					}
				}
			}else{
				// failed to append to rf
				return RC_FILE_WRITE_FAILED;
			}
			rid++;
	}
	btidx.close();
	rf.close(); // finished writing/appending to rf
	return 0;
}

RC SqlEngine::parseLoadLine(const string& line, int& key, string& value)
{
    const char *s;
    char        c;
    string::size_type loc;
    
    // ignore beginning white spaces
    c = *(s = line.c_str());
    while (c == ' ' || c == '\t') { c = *++s; }
	
    // get the integer key value
    key = atoi(s);
	
    // look for comma
    s = strchr(s, ',');
    if (s == NULL) { return RC_INVALID_FILE_FORMAT; }
	
    // ignore white spaces
    do { c = *++s; } while (c == ' ' || c == '\t');
    
    // if there is nothing left, set the value to empty string
    if (c == 0) { 
        value.erase();
        return 0;
    }
	
    // is the value field delimited by ' or "?
    if (c == '\'' || c == '"') {
        s++;
    } else {
        c = '\n';
    }
	
    // get the value string
    value.assign(s);
    loc = value.find(c, 0);
    if (loc != string::npos) { value.erase(loc); }
	
    return 0;
}
